package cn.stimd.spring.context.annotation;

import cn.stimd.spring.beans.BeanUtils;
import cn.stimd.spring.beans.factory.Aware;
import cn.stimd.spring.beans.factory.BeanFactory;
import cn.stimd.spring.beans.factory.BeanFactoryAware;
import cn.stimd.spring.beans.factory.annotation.AnnotatedBeanDefinition;
import cn.stimd.spring.beans.factory.config.BeanDefinition;
import cn.stimd.spring.beans.factory.config.BeanDefinitionHolder;
import cn.stimd.spring.beans.factory.support.AbstractBeanDefinition;
import cn.stimd.spring.beans.factory.support.BeanDefinitionRegistry;
import cn.stimd.spring.context.EnvironmentAware;
import cn.stimd.spring.context.ResourceLoaderAware;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;

/**
 * 该类实现了解析配置类的核心逻辑
 * <p>
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ConfigurationClassParser {
    private final Log logger = LogFactory.getLog(getClass());
    private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
            (o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());

    private final PropertySourceFactory propertySourceFactory = new DefaultPropertySourceFactory();
    private final MetadataReaderFactory metadataReaderFactory;
    private final Environment environment;
    private final ResourceLoader resourceLoader;
    private final BeanDefinitionRegistry registry;
    //已解析的配置类集合
    private final Set<ConfigurationClass> configurationClasses = new LinkedHashSet<>();
    //用于处理@ComponentScan注解
    private final ComponentScanAnnotationParser componentScanParser;
    //条件评估器，用于处理配置类的@Conditional注解
    private final ConditionEvaluator conditionEvaluator;
    //存储所有DeferredImportSelector，在配置类全部加载完后统一处理
    private List<DeferredImportSelectorHolder> deferredImportSelectors;


    public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, Environment environment,
                                    ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
        this.metadataReaderFactory = metadataReaderFactory;
        this.environment = environment;
        this.resourceLoader = resourceLoader;
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
        this.componentScanParser = new ComponentScanAnnotationParser(environment, registry);
    }


    /**
     * 解析引导配置类，唯一入口方法
     * @param configCandidates  引导配置类集合
     */
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        this.deferredImportSelectors = new LinkedList<>();

        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                //1. 通过注解声明的方式创建，根据注解元数据（AnnotationMetadata）来加载
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                //2. 通过编程的方式创建，比如RootBeanDefinition，根据class属性来加载
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                //3. 其余情况，BeanDefinition的class属性未确定，根据className来加载
                else{
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            } catch (IOException e) {
                throw new RuntimeException("解析configuration class [" + holder.getBeanName() + "]失败", e);
            }
        }

        // 处理延迟导入
        // ComponentScan和Import会加载配置类，多次执行processConfigurationClass
        // 其它流程都正常执行，只有DeferredImport会先存储起来
        // 直到所有的配置类都处理完毕，最后再执行需要延迟导入的选择器
        processDeferredImportSelectors();
    }


    //注解元数据已知，可能是反射或ASM的方式
    protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }

    //Class信息已知，通过反射的方式解析配置类
    protected final void parse(Class<?> clazz, String beanName) throws IOException {
        processConfigurationClass(new ConfigurationClass(clazz, beanName));
    }

    //类名已知，通过ASM的方式解析配置类
    protected final void parse(String className, String beanName) throws IOException {
        MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
        processConfigurationClass(new ConfigurationClass(reader.getAnnotationMetadata(), beanName));
    }


    /**
     * 对指定的配置类进行解析
     * @param configClass 表示一个配置类，有多种来源，可以是引导配置类、内部配置类、组件扫描的配置类、导入的配置类
     */
    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        //根据@Conditional判断是否应该处理配置类
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata())) {
            return;
        }

        //遍历配置类及其父类
        SourceClass sourceClass = asSourceClass(configClass);
        do{
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);
        //将已解析的配置类添加到集合中
        this.configurationClasses.add(configClass);
    }


    //处理延迟的Import
    private void processDeferredImportSelectors() {
        List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
        this.deferredImportSelectors = null;
        deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);  //排序

        for (DeferredImportSelectorHolder deferredImport : deferredImports) {
            ConfigurationClass configClass = deferredImport.getConfigurationClass();
            try {
                String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
                processImports(configClass, asSourceClass(configClass), asSourceClasses(imports));
            }
            catch (Throwable ex) {
                throw new RuntimeException("处理配置类[" + configClass.getMetadata().getClassName() + "]的DeferredImport候选者失败", ex);
            }
        }
    }


    /**
     * 解析配置类的核心逻辑
     * @param configClass   正在解析的配置类
     * @param sourceClass   配置类的相关信息
     * @return              返回配置类可能存在的父类，否则返回null
     */
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
        AnnotationMetadata metadata = configClass.getMetadata();
        //1. 处理内部类
        processMemberClasses(configClass, sourceClass);

        //2. 处理配置文件@PropertySource
        AnnotationAttributes propertySource = AnnotationConfigUtils.attributesFor(metadata, PropertySource.class);
        if(propertySource != null){
            processPropertySource(propertySource);
        }

        //3. 组件扫描@ComponentScan
        AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(metadata, ComponentScan.class);
        if(componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata())){
            Set<BeanDefinitionHolder> holders = this.componentScanParser.parse(componentScan, metadata.getClassName());

            //检查扫描的组件中是否存在Configuration类，如果存在递归处理
            for (BeanDefinitionHolder holder : holders) {
                if(ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)){
                    parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
                }
            }
        }

        //4. 处理导入@Import
        processImports(configClass, sourceClass, getImports(sourceClass));

        //5. 处理工厂方法@Bean
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass.getMetadata());
        for (MethodMetadata beanMethod : beanMethods) {
            //此处仅注册BeanMethod，加载由ConfigurationClassBeanDefinitionReader完成
            configClass.addBeanMethod(new BeanMethod(beanMethod, configClass));
        }

        //6. 处理父类
        if(sourceClass.getMetadata().hasSuperClass()){
            String superClass = sourceClass.getMetadata().getSuperClassName();
            if(!superClass.startsWith("java")){
                return sourceClass.getSuperClass();
            }
        }
        return null;
    }


    //step-1 处理内部类
    private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
        for (SourceClass memberClass : sourceClass.getMemberClasses()) {
            //判断内部类是不是一个配置类
            if(ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata())){
                processConfigurationClass(memberClass.asConfigClass(configClass));
            }
        }
    }


    //step-2 处理配置文件
    private void processPropertySource(AnnotationAttributes attrs) throws IOException {
        String name = attrs.getString("name");
        if (!StringUtils.hasLength(name)) {
            name = null;
        }

        String encoding = attrs.getString("encoding");
        if (!StringUtils.hasLength(encoding)) {
            encoding = null;
        }

        String[] locations = attrs.getStringArray("value");
        for (String location : locations) {
            try {
                String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);   //解析path上的${}占位符
                Resource resource = this.resourceLoader.getResource(resolvedLocation);
                addPropertySource(this.propertySourceFactory.createPropertySource(name, new EncodedResource(resource, encoding)));
            }catch (IOException e){
                logger.info("Properties location [" + location + "] not resolvable: " + e.getMessage());
                throw e;
            }
        }
    }

    private void addPropertySource(org.springframework.core.env.PropertySource<?> propertySource) {
        String name = propertySource.getName();
        MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
        if(!propertySources.contains(name)){
            propertySources.addLast(propertySource);
        }
    }


    //step-4 处理导入
    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates) {
        try {
            for (SourceClass candidate : importCandidates) {
                //1. 实现ImportSelector接口，返回全限定类名的数组，需要递归地调用processImports方法，直到以2或3的的方式来处理
                if (candidate.isAssignable(ImportSelector.class)) {
                    //加载类、实例化、调用感知接口设置相关的组件
                    Class<?> candidateClass = candidate.loadClass();
                    ImportSelector selector = (ImportSelector) BeanUtils.instantiateClass(candidateClass);
                    invokeAwareMethods(selector);

                    //将DeferredImportSelector加入缓存，稍后处理
                    if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                        this.deferredImportSelectors.add(new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                    } else{
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                        //递归处理返回的全限定类名数组
                        processImports(configClass, currentSourceClass, importSourceClasses);
                    }
                }
                //2. 实现ImportBeanDefinitionRegistrar接口的，缓存到ConfigurationClass稍后处理
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    Class<?> candidateClass = candidate.loadClass();
                    ImportBeanDefinitionRegistrar registrar = (ImportBeanDefinitionRegistrar) BeanUtils.instantiateClass(candidateClass);
                    invokeAwareMethods(registrar);
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                //3. 当作普通配置类，这里也是递归处理
                else{
                    processConfigurationClass(candidate.asConfigClass(configClass));
                }
            }
        }catch (Throwable e){
            throw new RuntimeException("处理配置类[" + configClass.getMetadata().getClassName() + "]的Import候选者失败", e);
        }
    }


    private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
        Set<SourceClass> imports = new LinkedHashSet<>();
        Set<SourceClass> visited = new LinkedHashSet<>();
        collectImports(sourceClass, imports, visited);
        return imports;
    }


    /**
     * 搜集需要导入的类
     * @param sourceClass   正在解析的配置类或注解类
     * @param imports       已缓存的导入类（最终返回的集合）
     * @param visited       等待添加的导入类（不一定能添加成功）
     */
    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException{
        // 尝试添加到set集合中，防止可能出现重复导入的情况
        if(visited.add(sourceClass)){
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();

                //遍历类的注解，如果注解不是Java注解或@Import，则尝试获取嵌套注解（即注解上的注解）
                if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                    collectImports(annotation, imports, visited);
                }
            }

            //将@Import注解指定的Class添加到集合中
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }


    private void invokeAwareMethods(Object parserStrategyBean) {
        if (parserStrategyBean instanceof Aware) {
            if (parserStrategyBean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) this.registry);
            }
            if (parserStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) parserStrategyBean).setEnvironment(this.environment);
            }
            if (parserStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(this.resourceLoader);
            }
        }
    }


    /**
     * step-5 检索BeanMethod的元数据
     * 按定义的顺序加载BeanMethod（在可能出现覆盖的情况下，BeanMethod的加载顺序非常重要）
     * 由于JVM的反射是无序的，为了确保BeanMethod按照声明的顺序加载，需要以ASM加载的方法顺序为基准，对BeanMethod进行排序
     */
    private Set<MethodMetadata> retrieveBeanMethodMetadata(AnnotationMetadata metadata) throws IOException {
        Set<MethodMetadata> beanMethods = metadata.getAnnotatedMethods(Bean.class.getName());

        // 如果BeanMethod的个数大于0，且配置类是以反射的形式加载的，需要对BeanMethod进行排序，确保按照声明的顺序来加载
        if (beanMethods.size() > 1 && metadata instanceof StandardAnnotationMetadata) {
            AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(metadata.getClassName()).getAnnotationMetadata();
            Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
            Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());

            //以AMS的方法顺序为准，将符合条件的BeanMethod加入集合中
            for (MethodMetadata asmMethod : asmMethods) {
                for (MethodMetadata beanMethod : beanMethods) {
                    if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
                        selectedMethods.add(beanMethod);
                        break;
                    }
                }
            }
            beanMethods = selectedMethods;
        }
        return beanMethods;
    }


    private Collection<SourceClass> asSourceClasses(String[] classNames) throws IOException {
        List<SourceClass> annotatedClasses = new ArrayList<>(classNames.length);
        for (String className : classNames) {
            annotatedClasses.add(asSourceClass(className));
        }
        return annotatedClasses;
    }


    //将ConfigurationClass转换成SourceClass，且总是通过ASM的方式加载的
    private SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
        //元数据的类型为StandardAnnotationMetadata，说明使用反射加载类，需要转换成ASM的方式
        AnnotationMetadata metadata = configurationClass.getMetadata();
        if (metadata instanceof StandardAnnotationMetadata) {
            return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass().getName());
        }
        //否则元数据类型为AnnotationMetadataReadingVisitor，说明是使用ASM加载的
        return asSourceClass(metadata.getClassName());
    }


    //通过ASM加载类
    SourceClass asSourceClass(String className) throws IOException {
        if(className.startsWith("java")){
            try {
                return new SourceClass(this.resourceLoader.getClassLoader().loadClass(className));
            } catch (ClassNotFoundException e) {
                throw new IOException(e);
            }
        }
        return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
    }


    public Set<ConfigurationClass> getConfigurationClasses() {
        return configurationClasses;
    }


    /**
     * 简单的包装类，不用关心注解声明的类是如何加载的，以统一的方式来解析
     * 1. 包括当前类（配置类）、父类、内部类、注解（Import）、以及注解的属性值（比如@Import(EnableAutoConfigurationImportSelector.class)）
     * 2. 重写equals方法和hashcode方法，做防重处理
     */
    private class SourceClass {
        //source有两种类型，Class表示类是通过反射加载的，MetadataReader表示通过ASM加载的
        private final Object source;
        private final AnnotationMetadata metadata;

        public SourceClass(Object source) {
            this.source = source;
            if (source instanceof Class) {
                this.metadata = new StandardAnnotationMetadata((Class<?>) source, true);
            }
            else {
                this.metadata = ((MetadataReader) source).getAnnotationMetadata();
            }
        }

        //获取该类的所有注解
        public Set<SourceClass> getAnnotations() {
            Set<SourceClass> result = new LinkedHashSet<>();
            for (String className : this.metadata.getAnnotationTypes()) {
                try {
                    result.add(asSourceClass(className));
                }
                catch (Throwable ex) { }
            }
            return result;
        }

        //获取指定注解上的指定属性值（即@Import的value属性），并将属性值转换成SourceLClass
        public Collection<SourceClass> getAnnotationAttributes(String annType, String attribute) throws IOException {
            Map<String, Object> annotationAttributes = this.metadata.getAnnotationAttributes(annType, true);
            if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) {
                return Collections.emptySet();
            }
            String[] classNames = (String[]) annotationAttributes.get(attribute);
            Set<SourceClass> result = new LinkedHashSet<>();
            for (String className : classNames) {
                result.add(asSourceClass(className));       //使用Import导入的类也作为SourceClass被处理
            }
            return result;
        }

        //获取父类
        public SourceClass getSuperClass() throws IOException {
            if (this.source instanceof Class) {
                return new SourceClass(((Class<?>) this.source).getSuperclass());
            }
            return asSourceClass(((MetadataReader) this.source).getClassMetadata().getSuperClassName());
        }

        //获取所有的内部类
        public Collection<SourceClass> getMemberClasses() throws IOException {
            Object sourceToProcess = this.source;
            //如果source的类型是Class，则通过反射获取成员内部类
            if (sourceToProcess instanceof Class) {
                Class<?> sourceClass = (Class<?>) sourceToProcess;
                Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
                List<SourceClass> members = new ArrayList<>(declaredClasses.length);

                for (Class<?> declaredClass : declaredClasses) {
                    members.add(new SourceClass(declaredClass));    //把内部类转换成SourceClass
                }
                return members;
            }

            //使用ASM来解析
            MetadataReader sourceReader = (MetadataReader) sourceToProcess;
            String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames();
            List<SourceClass> members = new ArrayList<>(memberClassNames.length);
            for (String memberClassName : memberClassNames) {
                members.add(asSourceClass(memberClassName));
            }
            return members;
        }

        public boolean isAssignable(Class<?> clazz) throws IOException {
            if (this.source instanceof Class) {
                return clazz.isAssignableFrom((Class<?>) this.source);
            }
            return new AssignableTypeFilter(clazz).match((MetadataReader) this.source, metadataReaderFactory);
        }

        public Class<?> loadClass() throws ClassNotFoundException {
            if (this.source instanceof Class) {
                return (Class<?>) this.source;
            }
            String className = ((MetadataReader) this.source).getClassMetadata().getClassName();
            return resourceLoader.getClassLoader().loadClass(className);
        }

        //当前SourceClass转换成ConfigurationClass，表明该配置类是由其他配置类导入的
        public ConfigurationClass asConfigClass(ConfigurationClass importedBy) {
            if (this.source instanceof Class) {
                return new ConfigurationClass((Class<?>) this.source, importedBy);
            }
            return new ConfigurationClass((MetadataReader) this.source, importedBy);
        }

        //获取注解元数据
        public AnnotationMetadata getMetadata() {
            return metadata;
        }

        @Override
        public boolean equals(Object other) {
            return (this == other || (other instanceof SourceClass &&
                    this.metadata.getClassName().equals(((SourceClass) other).metadata.getClassName())));
        }

        @Override
        public int hashCode() {
            return this.metadata.getClassName().hashCode();
        }

        @Override
        public String toString() {
            return this.metadata.getClassName();
        }
    }


    //内部类，存储延迟导入接口
    private static class DeferredImportSelectorHolder {
        private final ConfigurationClass configurationClass;
        private final DeferredImportSelector importSelector;

        public DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) {
            this.configurationClass = configClass;
            this.importSelector = selector;
        }

        public ConfigurationClass getConfigurationClass() {
            return this.configurationClass;
        }

        public DeferredImportSelector getImportSelector() {
            return this.importSelector;
        }
    }
}
